package fr.lip6.move.care.sdmaker.jvmmodel

import com.google.inject.Inject
import org.eclipse.xtext.xbase.jvmmodel.AbstractModelInferrer
import org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor
import org.eclipse.xtext.xbase.jvmmodel.JvmTypesBuilder
import fr.lip6.move.care.sdmaker.sDMaker.SDMakerModel
import java.util.ArrayList
import fr.lip6.move.care.sdmaker.sDMaker.Lifeline
import fr.lip6.move.care.sdmaker.sDMaker.Declaration
import org.eclipse.xtext.common.types.JvmFormalParameter
import fr.lip6.move.care.sdmaker.sDMaker.Diagram
import fr.lip6.move.care.sdmaker.sDMaker.Element
import fr.lip6.move.care.sdmaker.sDMaker.Call
import fr.lip6.move.care.sdmaker.sDMaker.Alt
import fr.lip6.move.care.sdmaker.sDMaker.Opt
import fr.lip6.move.care.sdmaker.sDMaker.Loop
import org.eclipse.xtext.common.types.JvmDeclaredType
import com.google.inject.internal.Initializer$InjectableReference
import org.eclipse.emf.codegen.util.ImportManager
import org.eclipse.xtext.common.types.impl.JvmTypeReferenceImplCustom
import org.eclipse.xtext.xbase.compiler.JvmModelGenerator
import org.eclipse.xtext.xbase.typing.XbaseTypeProvider
import org.eclipse.xtext.common.types.util.TypeReferences
import org.eclipse.xtext.common.types.JvmGenericType
import org.eclipse.xtext.xbase.lib.Procedures$Procedure1
import org.eclipse.xtext.xbase.jvmmodel.IJvmModelInferrer
import org.eclipse.xtext.xbase.lib.Procedures
import java.io.FileNotFoundException
import java.io.IOException

/**
 * <p>Infers a JVM model from the source model.</p> 
 *
 * <p>The JVM model should contain all elements that would appear in the Java code 
 * which is generated from the source model. Other models link against the JVM model rather than the source model.</p>     
 */
class SDMakerJvmModelInferrer extends AbstractModelInferrer {
    
    /**
     *  convenience API to build and initialize JVM types and their members.
     */
	@Inject extension JvmTypesBuilder

        
    /**
     * SDMaker global variables
     */
    ArrayList<String> types;
    private JvmGenericType actor;
    private JvmGenericType alt;
    private JvmGenericType loop;
    private JvmGenericType opt;
    private JvmGenericType main;
    private JvmGenericType block;
    private JvmGenericType blockList;
    private JvmGenericType call;
    private JvmGenericType epsilonRemover;
    private JvmGenericType generateDOT;
    private JvmGenericType FSA;
    private JvmGenericType method;
    private JvmGenericType objectInstance;
    private JvmGenericType objectClass;
    private JvmGenericType statement;
    private JvmGenericType trace;
    private JvmGenericType stringTab;

	/**
	 * The dispatch method {@code infer} is called for each instance of the
	 * given element's type that is contained in a resource.
	 * 
	 * @param element
	 *            the model to create one or more
	 *            {@link org.eclipse.xtext.common.types.JvmDeclaredType declared
	 *            types} from.
	 * @param acceptor
	 *            each created
	 *            {@link org.eclipse.xtext.common.types.JvmDeclaredType type}
	 *            without a container should be passed to the acceptor in order
	 *            get attached to the current resource. The acceptor's
	 *            {@link IJvmDeclaredTypeAcceptor#accept(org.eclipse.xtext.common.types.JvmDeclaredType)
	 *            accept(..)} method takes the constructed empty type for the
	 *            pre-indexing phase. This one is further initialized in the
	 *            indexing phase using the closure you pass to the returned
	 *            {@link org.eclipse.xtext.xbase.jvmmodel.IJvmDeclaredTypeAcceptor.IPostIndexingInitializing#initializeLater(org.eclipse.xtext.xbase.lib.Procedures.Procedure1)
	 *            initializeLater(..)}.
	 * @param isPreIndexingPhase
	 *            whether the method is called in a pre-indexing phase, i.e.
	 *            when the global index is not yet fully updated. You must not
	 *            rely on linking using the index if isPreIndexingPhase is
	 *            <code>true</code>.
	 */ 
    private int i; 
    def dispatch void infer(SDMakerModel model, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase){ 
        
        if(model != null && model.diagrams.size() > 0){
          i = 0;    
          
          //initImportedCareConcepts(model, acceptor)
          findAllModelTypes(model)
          compileInterfaces(model,  acceptor, isPreIndexingPhase)
          
//          acceptor.accept(model.toClass("Model"))
//                  .initializeLater([
					/*	TODO temporairement mettre en commentaire
          			for(d: model.diagrams){
            			compileCare(d, acceptor, isPreIndexingPhase)
          			} 
          			
          			*/       	
//                  ])
          
        }
      }
    
        def void initImportedCareConcepts(SDMakerModel model, IJvmDeclaredTypeAcceptor acceptor)
    {

        actor= model.toClass("Actor")
        alt= model.toClass("Alt")
        loop= model.toClass("Loop")
        opt= model.toClass("Opt")
        main= model.toClass("Main")
        block= model.toClass("Block");
        blockList= model.toClass("BlockList");
        call= model.toClass("Call");
        epsilonRemover= model.toClass("EpsilonRemover");
        generateDOT= model.toClass("GenerateDOT");
        FSA= model.toClass("FSA");
        method= model.toClass("Method");
        objectInstance= model.toClass("ObjectInstance");
        objectClass= model.toClass("ObjectClass");
        statement= model.toClass("Statement");
        trace= model.toClass("Trace");
        stringTab= model.toClass("String[]");
        
        acceptor.accept(actor).initializeLater([ packageName="program"]);
        acceptor.accept(alt).initializeLater([ packageName="program"]);
        acceptor.accept(loop).initializeLater([ packageName="program"]);
        acceptor.accept(opt).initializeLater([ packageName="program"]);
        acceptor.accept(main).initializeLater([ packageName="program"]);
        acceptor.accept(call).initializeLater([ packageName="program"]);
        acceptor.accept(block).initializeLater([ packageName="program"]);
        acceptor.accept(blockList).initializeLater([ packageName="program"]);
        acceptor.accept(epsilonRemover).initializeLater([ packageName="fsa"]);
        acceptor.accept(generateDOT).initializeLater([ packageName="fsa"]);
        acceptor.accept(FSA).initializeLater([ packageName="fsa"]);
        acceptor.accept(method).initializeLater([ packageName="traces"]);
        acceptor.accept(objectInstance).initializeLater([ packageName="traces"]);
        acceptor.accept(objectClass).initializeLater([ packageName="traces"]);
        acceptor.accept(statement).initializeLater([ packageName="traces"]);
        acceptor.accept(trace).initializeLater([ packageName="traces"]);
    }
    
    def findAllModelTypes(SDMakerModel model){
        
        var Boolean alreadySeenThisType= false;
        types= new ArrayList<String>();
        
        for(d: model.diagrams){
            for(elt: d.elements){
                if(elt instanceof Lifeline &&
                   (elt as Lifeline).kind.compareTo("Object")==0){
                    
                    var Lifeline obj= elt as Lifeline
                    alreadySeenThisType = false;
                    
                    for(type: types){
                        if(obj.type.compareTo(type) == 0){
                                alreadySeenThisType = true;
                         }   
                    }
            
                    if(alreadySeenThisType == false){
                        types.add(obj.type);
                    }
                }
            }
        }
    }
    
    def compileInterfaces(SDMakerModel model,
        IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase){
        
        for(String type: types){
            acceptor.accept(model.toInterface(type, null))
                    .initializeLater([
                        for (elt : model.eAllContents
                                         .toIterable
                                         .filter(typeof(Declaration))){
                                var Declaration dcl= elt as Declaration
                                if(dcl.typeOwner.compareTo(type)==0){
                                    members += dcl.toMethod(dcl.name, 
                                                            dcl.returnType)[
                                        documentation = (elt as Declaration).documentation 
                                        for(JvmFormalParameter p: (elt as Declaration).params){
                                            parameters += p.toParameter(p.name, p.parameterType);
                                        }
                                        setAbstract(true)
                                    ]         
                                }
                        }
                    ])
        }
    }
    
    @Inject TypeReferences typeReferences
 
//      // transformer diagramme a CARE
    def compileCare(Diagram d, IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase) {

        acceptor.accept(d.toClass(d.name))
                .initializeLater([
					packageName = "programs"

                    documentation = d.documentation
                    
                    //adding foo field for import
                    members += d.toField("importFile", newTypeRef(typeof(java.io.File)))[
                        documentation= "Fields for hack-imports only"
                    ]
                    members += d.toField("importFileNotFoundException", newTypeRef(typeof(java.io.FileNotFoundException)))
                    members += d.toField("importIOException", newTypeRef(typeof(java.io.IOException)))
                    members += d.toField("importArrayList", newTypeRef(typeof(java.util.ArrayList)))
                    
                    members += d.toField("importEpsilonRemover", newTypeRef(epsilonRemover))
                    members += d.toField("importGenerateDOT", newTypeRef(generateDOT))
                    members += d.toField("importFSA", newTypeRef(FSA))
                    
                    members += d.toField("importActor", newTypeRef(actor))
                    members += d.toField("importAlt", newTypeRef(alt))
                    members += d.toField("importLoop", newTypeRef(loop))
                    members += d.toField("importOpt", newTypeRef(opt))
                    members += d.toField("importBlock", newTypeRef(block))
                    members += d.toField("importBlockList", newTypeRef(blockList))
                    members += d.toField("importCall", newTypeRef(call))
                    
                    members += d.toField("importMethod", newTypeRef(method))
                    members += d.toField("importObjectInstance", newTypeRef(objectInstance))
                    members += d.toField("importObjectClass", newTypeRef(objectClass))
                    members += d.toField("importStatement", newTypeRef(statement))
                    members += d.toField("importTrace", newTypeRef(trace))
                    
                    members += d.toMethod("create", newTypeRef(main))[
                        documentation = d.documentation 
                        setStatic(true);
                        body = [
                            append(
                            ''' 
                            int i= 0;
                            ArrayList<Actor> actors= new ArrayList<Actor>();
                            ''')
                            
                            var ArrayList<CompileResult> allCompRes= new ArrayList<CompileResult>()
                            for(elt : d.elements) {
                                var CompileResult compRes= compileElt(elt)  
                                allCompRes.add(compRes)                             
                                append(compRes.charSequenceResult)
                            } 
                            
                            //Creation du point d'entrée: C'est le 1er element
                            //du diagramme pour le moment
                            var String mainID= "entry_point_id"+(i=i+1)
                            append(
                            '''
                            
                            ArrayList<Block> «mainID» = new ArrayList<Block>();
                            «FOR elt: allCompRes»
                            «IF elt.isAddableToEntryPoint»
                            «mainID».add(«elt.eltID»);
                            «ENDIF»
                            «ENDFOR»
                            return new Main(«mainID», actors, 200);
                            ''')    
                    ]
                ]
                members += d.toMethod("main", null)[
                    setStatic(true);
                   // parameters += d.toParameter("args", newTypeRef(stringTab))
                    body= [
                        append('''
                            Main sd=create();
                            boolean ok=sd.saveModel("«d.name»");
                            if (ok){
                                sd.genereAllTraces("«d.name»", 100);
                            }
                        ''')
                    ]
                ]
            ])
    }
    
    /* ***********************
     * Methode d'aiguillage **
     * ***********************/
    def CompileResult compileElt(Element elt) {
        switch elt {
            Lifeline : {
                if((elt as Lifeline).kind.compareTo("Actor")==0){
                    compileActor(elt)                    
                }
                else{
                    compileObject(elt)
                }
            }
            Call : {
                compileCall(elt)
            }
            Alt : {
                compileAlt(elt)
            }
            Loop : {
                compileLoop(elt)
            }
            Opt : {
                compileOpt(elt)
            }
            default: {
                CompileResult::emptyResult
            }
        }
    }

    /* ***************************
     * Compilation des acteurs  **
     * ***************************/
    def CompileResult compileActor(Lifeline actor) {
    
    var CharSequence charSequenceResult=
    '''    
                                      
    ObjectClass «actor.name»Class=new ObjectClass("«actor.name»");
    Actor «actor.name» = new Actor("«actor.name»", «actor.name»Class);   
    actors.add(«actor.name»);                                                 
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(actor.name)
                .setIsAddableToEntryPoint(false)
    }
    
    /* ***************************
     * Compilation des objets ****
     * ***************************/
    def CompileResult compileObject(Lifeline obj) {
        
      return compileActor(obj) //HACK  
       
//    var CharSequence charSequenceResult=
//    ''''''
//        
//    return new CompileResult()
//                .setCharSequenceResult(charSequenceResult)
//                .setEltID(obj.name)
//                .setIsAddableToEntryPoint(false)
    }

    /* *************************************
     * Compilation des appels de methode ***
     * *************************************/
    def CompileResult compileCall(Call call) {
    var ArrayList<String> nestedEltIDs= new ArrayList<String>()
    var CompileResult compRes
    var callID= call.name+"_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
    
    «IF call.nested_calls.size() == 0»
    Call «callID» = new Call(new Statement("S" + i++,«call.caller»,new Method("«call.name»",new ArrayList<ObjectInstance>(),«call.called»Class),«call.called»));
    «ELSE»
    /* NESTED CALL: «callID» */
    BlockList bloc_«callID»= new BlockList();
    «FOR elt: call.nested_calls»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileElt(elt))»
    «SDMakerJvmModelUtils::toNullExp(nestedEltIDs.add(compRes.eltID))»
    «compRes.charSequenceResult»
    «ENDIF»
    «ENDFOR»
    
    /* add(s) for «callID» */
    «FOR eltID: nestedEltIDs»
    bloc_«callID».add(«eltID»);
    «ENDFOR»
    
    Call «callID» = new Call(new Statement("S" + i++,«call.caller»,new Method("«call.name»",new ArrayList<ObjectInstance>(),«call.called»Class),«call.called»), bloc_«call.name»);
    «ENDIF»
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(callID)
                .setIsAddableToEntryPoint(true)
    }

    /* *************************************
     * Compilation des alternatives  *******
     * *************************************/
    def CompileResult compileAlt(Alt alt) { 
    var ArrayList<String> conseqNestedEltIDs= new ArrayList<String>()
    var ArrayList<String> alterNestedEltIDs= new ArrayList<String>()
    var CompileResult compRes
    var altID= "alt_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''

    /* ALTERNATIVE: «altID» */
    BlockList bloc_«altID»_conseq = new BlockList();
    «FOR elt: alt.conseq»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileElt(elt))»
    «SDMakerJvmModelUtils::toNullExp(conseqNestedEltIDs.add(compRes.eltID))»
    «compRes.charSequenceResult»
    «ENDIF»
    «ENDFOR»

    «IF conseqNestedEltIDs.size() > 0»
    // add(s) for bloc_«altID»_conseq
    «FOR eltID: conseqNestedEltIDs»
    bloc_«altID»_conseq.add(«eltID»);
    «ENDFOR»
    «ENDIF»

    BlockList bloc_«altID»_alter = new BlockList();
    «FOR elt: alt.alter»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileElt(elt))»
    «SDMakerJvmModelUtils::toNullExp(alterNestedEltIDs.add(compRes.eltID))»
    «compRes.charSequenceResult»
    «ENDIF»
    «ENDFOR»

    «IF alterNestedEltIDs.size() > 0»
    // add(s) for bloc_«altID»_alter
    «FOR eltID: alterNestedEltIDs»
    bloc_«altID»_alter.add(«eltID»);
    «ENDFOR»
    «ENDIF»

    // adds for «altID»
    Alt «altID»= new Alt();
    «altID».add(bloc_«altID»_conseq);
    «altID».add(bloc_«altID»_alter);
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(altID)
                .setIsAddableToEntryPoint(true)
   }

    /* *************************************
     * Compilation des boucles *************
     * *************************************/
    def CompileResult compileLoop(Loop loop) {
    var ArrayList<String> nestedEltIDs= new ArrayList<String>();
    var CompileResult compRes
    var loopID= "loop_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
    
    /* LOOP: «loopID» */
    BlockList bloc_«loopID» = new BlockList();
    «FOR elt: loop.body»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileElt(elt))»
    «SDMakerJvmModelUtils::toNullExp(nestedEltIDs.add(compRes.eltID))»
    «compRes.charSequenceResult»
    «ENDIF»
    «ENDFOR»
    
    «IF nestedEltIDs.size() > 0»
    //add(s) for «loopID»
    «FOR eltID: nestedEltIDs»
    bloc_«loopID».add(«eltID»);
    «ENDFOR»
    «ENDIF»
    Loop «loopID» = new Loop(bloc_«loopID»);
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(loopID)
                .setIsAddableToEntryPoint(true)
    }

    /* *************************************
     * Compilation des options *************
     * *************************************/
    def CompileResult compileOpt(Opt opt) {
    var ArrayList<String> nestedEltIDs= new ArrayList<String>();
    var CompileResult compRes
    var optID= "opt_id"+(i=i+1)

    var CharSequence charSequenceResult=
    '''
    
    /* OPTION: «optID» */
    BlockList bloc_«optID» = new BlockList();
    «FOR elt: opt.opts»
    «IF !(elt instanceof Declaration)»
    «SDMakerJvmModelUtils::toNullExp(compRes= compileElt(elt))»
    «SDMakerJvmModelUtils::toNullExp(nestedEltIDs.add(compRes.eltID))»
    «compRes.charSequenceResult»
    «ENDIF»
    «ENDFOR»
    
    «IF nestedEltIDs.size() > 0»
    //add(s) for «optID»
    «FOR eltID: nestedEltIDs»
    bloc_«optID».add(«eltID»);
    «ENDFOR»
    «ENDIF»
    
    Opt «optID» = new Opt(bloc_«optID»);
    '''
    
    return new CompileResult()
                .setCharSequenceResult(charSequenceResult)
                .setEltID(optID)
                .setIsAddableToEntryPoint(true)
    }
//    
//    def CompileResult findEntryPoint(ArrayList<CompileResult> allResult,
//        IJvmDeclaredTypeAcceptor acceptor, boolean isPreIndexingPhase,
//    Diagram d){
//        var CompileResult compRes= CompileResult::emptyResult
//        var Boolean found= false;
//        var int i= 0
//       
//        while(found==false && i < allResult.size()){
//            
//            if(allResult.get(i).getEltKind().compareTo("Call") == 0 ||
//               allResult.get(i).getEltKind().compareTo("Alt") == 0 ||
//               allResult.get(i).getEltKind().compareTo("Loop") == 0 ||
//               allResult.get(i).getEltKind().compareTo("Opt") == 0){
//                   compRes= allResult.get(i)
//                   found=true
//            }
//            i=i+1
//        }
//        
//        return compRes
//    }

}
